/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright (c) 2019-2020 DilOS
 */

#include <sys/zfs_context.h>
#include <sys/spa_impl.h>
#include <sys/vdev_impl.h>
#include <sys/spa.h>
#include <zfs_comutil.h>

/* pool kstat */
typedef struct spa_state_kstat_values {
	kstat_named_t ssk_state;
	kstat_named_t ssk_guid;
	kstat_named_t ssk_size;
	kstat_named_t ssk_alloc;
	kstat_named_t ssk_free;
	kstat_named_t ssk_dedup_ratio;
#if 0
	kstat_named_t ssk_zfs_available;
	kstat_named_t ssk_zfs_used;
	kstat_named_t ssk_zfs_size;
#endif
	kstat_named_t ssk_syncing_txg;
	kstat_named_t ssk_mmp_ub_timestamp;
} spa_state_kstat_values_t;

static spa_state_kstat_values_t empty_spa_state_kstats = {
	{ "state",		KSTAT_DATA_CHAR },
	{ "guid",		KSTAT_DATA_UINT64 },
	{ "zpool_size",		KSTAT_DATA_UINT64 },
	{ "zpool_allocated",	KSTAT_DATA_UINT64 },
	{ "zpool_free",		KSTAT_DATA_UINT64 },
	{ "dedup_ratio",	KSTAT_DATA_CHAR },
#if 0
	{ "zfs_available",	KSTAT_DATA_UINT64 },
	{ "zfs_used",		KSTAT_DATA_UINT64 },
	{ "zfs_size",		KSTAT_DATA_UINT64 },
#endif
	{ "syncing_txg",	KSTAT_DATA_UINT64 },
	{ "mmp_ub_timestamp",	KSTAT_DATA_UINT64 },
};

#if 0 /* old API */
static void *
spa_state_addr(kstat_t *ksp, loff_t n)
{
	return (ksp->ks_private);	/* return the spa_t */
}

static int
spa_state_data(char *buf, size_t size, void *data)
{
	spa_t *spa = (spa_t *)data;
	(void) snprintf(buf, size, "%s\n", spa_state_to_name(spa));
	return (0);
}
#endif

static int
spa_state_update(kstat_t *ksp, int rw)
{
	if (rw == KSTAT_WRITE)
		return (EACCES);

	if (ksp == NULL)
		return (0);

	spa_t *spa = ksp->ks_private;

	if (spa == NULL)
		return (0);

	spa_state_kstat_values_t *spa_state_kstats = ksp->ks_data;
	(void) snprintf(spa_state_kstats->ssk_state.value.c,
	    sizeof (spa_state_kstats->ssk_state.value.c), "%s",
	    spa_state_to_name(spa));

	spa_state_kstats->ssk_guid.value.ui64 = spa_guid(spa);
	spa_state_kstats->ssk_syncing_txg.value.ui64 = spa_syncing_txg(spa);

	if (spa->spa_stats.state.destroy)
		return (0);

	if (spa->spa_state == POOL_STATE_DESTROYED)
		return (0);

	if (spa->spa_root_vdev == NULL)
		return (0);

	if (spa_load_state(spa) == SPA_LOAD_NONE &&
		!spa->spa_is_exporting) {

		metaslab_class_t *mc = spa_normal_class(spa);

		uint64_t alloc = metaslab_class_get_alloc(mc);
		alloc += metaslab_class_get_alloc(spa_special_class(spa));
		alloc += metaslab_class_get_alloc(spa_dedup_class(spa));
		spa_state_kstats->ssk_alloc.value.ui64 = alloc;

		uint64_t size = metaslab_class_get_space(mc);
		size += metaslab_class_get_space(spa_special_class(spa));
		size += metaslab_class_get_space(spa_dedup_class(spa));
		spa_state_kstats->ssk_size.value.ui64 = size;

		spa_state_kstats->ssk_free.value.ui64 =
		    spa_state_kstats->ssk_size.value.ui64 -
		    spa_state_kstats->ssk_alloc.value.ui64;

		mmp_thread_t *mmp = &spa->spa_mmp;
		if (mmp != NULL) {
		spa_state_kstats->ssk_mmp_ub_timestamp.value.ui64 =
		    mmp->mmp_ub.ub_timestamp;
		}

		uint64_t pool_dedup_ratio = ddt_get_pool_dedup_ratio(spa);

		(void) snprintf(
		    spa_state_kstats->ssk_dedup_ratio.value.c,
		    sizeof (spa_state_kstats->ssk_state.value.c),
		    "%llu.%02llu",
		    (u_longlong_t)(pool_dedup_ratio / 100),
		    (u_longlong_t)(pool_dedup_ratio % 100));

/* FIXME: deadlock */
#if 0
		objset_t *os;
		int error = dmu_objset_hold(
		    spa_name(spa), spa, &os);

		if (error == 0) {
			spa_state_kstats->ssk_zfs_available.value.ui64 =
			    dsl_get_available(dmu_objset_ds(os));
			spa_state_kstats->ssk_zfs_used.value.ui64 =
			     dsl_get_used(dmu_objset_ds(os));
			dmu_objset_rele(os, spa);

			spa_state_kstats->ssk_zfs_size.value.ui64 =
			    spa_state_kstats->ssk_zfs_used.value.ui64 +
			    spa_state_kstats->ssk_zfs_available.value.ui64;
		}
#endif

	}

	return (0);
}

/*
 * Return the state of the pool in kstat <pool> state.
 *
 * This is a lock-less read of the pool's state (unlike using 'zpool', which
 * can potentially block for seconds).  Because it doesn't block, it can useful
 * as a pool heartbeat value.
 */
static void
spa_state_init(spa_t *spa)
{
	spa_history_kstat_t *shk = &spa->spa_stats.state;
	char name[KSTAT_STRLEN];
	kstat_t *ksp;

	if (strcmp(spa->spa_name, TRYIMPORT_NAME) == 0)
		return;

	mutex_init(&shk->lock, NULL, MUTEX_DEFAULT, NULL);
	int n = snprintf(name, sizeof (name), "%s",  spa_name(spa));
	if (n < 0) {
		zfs_dbgmsg("failed to create spa_state kstat\n");
		return;
	}

	ksp = kstat_create(name, 0, "status", "zpool",
	    KSTAT_TYPE_NAMED,
	    sizeof (spa_state_kstat_values_t) / sizeof (kstat_named_t),
	    KSTAT_FLAG_VIRTUAL);

	shk->kstat = ksp;
	if (ksp == NULL)
		return;

	spa_state_kstat_values_t *spa_state_kstats =
	   kmem_alloc(sizeof (empty_spa_state_kstats), KM_SLEEP);
	memcpy(spa_state_kstats, &empty_spa_state_kstats,
	    sizeof (empty_spa_state_kstats));

	shk->destroy = B_FALSE;
	ksp->ks_lock = &shk->lock;
	ksp->ks_data = spa_state_kstats;
	ksp->ks_update = spa_state_update;
	ksp->ks_private = spa;
	//kstat_set_raw_ops(ksp, NULL, spa_state_data, spa_state_addr);
	kstat_install(ksp);
}

static void
spa_state_destroy(spa_t *spa)
{
	spa_history_kstat_t *shk = &spa->spa_stats.state;
	kstat_t *ksp = shk->kstat;

	if (ksp == NULL)
		return;

	shk->destroy = B_TRUE;
	mutex_enter(&shk->lock);
	mutex_exit(&shk->lock);
	if (ksp->ks_data != NULL) {
		kmem_free(ksp->ks_data, sizeof (empty_spa_state_kstats));
		ksp->ks_data = NULL;
	}
	kstat_delete(ksp);
	shk->kstat = NULL;

	mutex_destroy(&shk->lock);
}

void
spa_stats_init(spa_t *spa)
{
//	spa_read_history_init(spa);
//	spa_txg_history_init(spa);
//	spa_tx_assign_init(spa);
//	spa_io_history_init(spa);
//	spa_mmp_history_init(spa);
	spa_state_init(spa);
//	spa_iostats_init(spa);
}

void
spa_stats_destroy(spa_t *spa)
{
//	spa_iostats_destroy(spa);
	spa_state_destroy(spa);
//	spa_tx_assign_destroy(spa);
//	spa_txg_history_destroy(spa);
//	spa_read_history_destroy(spa);
//	spa_io_history_destroy(spa);
//	spa_mmp_history_destroy(spa);
}

/* vdev kstat */
typedef struct vdev_state_kstat_values {
	kstat_named_t vsk_state;
	kstat_named_t vsk_type;
	kstat_named_t vsk_vdev_path;
	kstat_named_t vsk_size;
	kstat_named_t vsk_vid;
	kstat_named_t vsk_pid;
	kstat_named_t vsk_serial;
	kstat_named_t vsk_islog;
	kstat_named_t vsk_isspare;
	kstat_named_t vsk_isl2cache;
	kstat_named_t vsk_devid;
	kstat_named_t vsk_guid;
	kstat_named_t vsk_nonrot;
	kstat_named_t vsk_vs_read_errors;
	kstat_named_t vsk_vs_write_errors;
	kstat_named_t vsk_vs_checksum_errors;
	kstat_named_t vsk_vs_slow_ios;
	kstat_named_t vsk_alloc;
	kstat_named_t vsk_space;
	kstat_named_t vsk_rsize;
	kstat_named_t vsk_esize;
	kstat_named_t vsk_vdev_ashift;
	kstat_named_t vsk_hba;
	kstat_named_t vsk_enclosure;
	kstat_named_t vsk_slot;

} vdev_state_kstat_values_t;

static vdev_state_kstat_values_t empty_vdev_state_kstats = {
	{ "state",	KSTAT_DATA_CHAR },
	{ "type",	KSTAT_DATA_CHAR },
	{ "vdev_path",	KSTAT_DATA_STRING },
	{ "size",	KSTAT_DATA_ULONGLONG },
	{ "vid",	KSTAT_DATA_STRING },
	{ "pid",	KSTAT_DATA_STRING },
	{ "serial",	KSTAT_DATA_STRING },
	{ "islog",	KSTAT_DATA_INT32 },
	{ "isspare",	KSTAT_DATA_INT32 },
	{ "isl2cache",	KSTAT_DATA_INT32 },
	{ "devid",	KSTAT_DATA_STRING },
	{ "guid",	KSTAT_DATA_STRING },
	{ "nonrot",	KSTAT_DATA_INT32 },
	{ "errors_read",	KSTAT_DATA_UINT64 },
	{ "errors_write",	KSTAT_DATA_UINT64 },
	{ "errors_checksum",	KSTAT_DATA_UINT64 },
	{ "errors_slow",	KSTAT_DATA_UINT64 },
	{ "alloc",	KSTAT_DATA_UINT64 },
	{ "space",	KSTAT_DATA_UINT64 },
	{ "rsize",	KSTAT_DATA_UINT64 },
	{ "esize",	KSTAT_DATA_UINT64 },
	{ "vdev_ashift",	KSTAT_DATA_UINT64 },
	{ "hba",	KSTAT_DATA_STRING },
	{ "enclosure",	KSTAT_DATA_INT32 },
	{ "slot",	KSTAT_DATA_INT32 },

};


static int
vdev_state_update(kstat_t *ksp, int rw)
{
	if (rw == KSTAT_WRITE)
		return (EACCES);

	if (ksp == NULL)
		return (0);

	vdev_t *vd = ksp->ks_private;
	if (vd == NULL)
		return (0);

	if (vd->vdev_kstat_destroy)
		return (0);

	vdev_state_kstat_values_t *vdev_state_kstats = ksp->ks_data;
	(void) snprintf(vdev_state_kstats->vsk_state.value.c,
	    sizeof (vdev_state_kstats->vsk_state.value.c), "%s",
	    vdev_state_to_name(vd));

	vdev_state_kstats->vsk_islog.value.i32 = vd->vdev_islog;
	vdev_state_kstats->vsk_isspare.value.i32 = vd->vdev_isspare;
	vdev_state_kstats->vsk_isl2cache.value.i32 = vd->vdev_isl2cache;
	vdev_state_kstats->vsk_size.value.ull = vd->vdev_max_psize;
	vdev_state_kstats->vsk_nonrot.value.i32 = vd->vdev_nonrot;

	vdev_state_kstats->vsk_vs_read_errors.value.ui64 =
	    vd->vdev_stat.vs_read_errors;
	vdev_state_kstats->vsk_vs_write_errors.value.ui64 =
	    vd->vdev_stat.vs_write_errors;
	vdev_state_kstats->vsk_vs_checksum_errors.value.ui64 =
	    vd->vdev_stat.vs_checksum_errors;
	vdev_state_kstats->vsk_vs_slow_ios.value.ui64 =
	    vd->vdev_stat.vs_slow_ios;
	vdev_state_kstats->vsk_alloc.value.ui64 = vd->vdev_stat.vs_alloc;
	vdev_state_kstats->vsk_space.value.ui64 = vd->vdev_stat.vs_space;
	vdev_state_kstats->vsk_rsize.value.ui64 = vd->vdev_stat.vs_rsize;
	vdev_state_kstats->vsk_esize.value.ui64 = vd->vdev_stat.vs_esize;
	vdev_state_kstats->vsk_vdev_ashift.value.ui64 = vd->vdev_ashift;
	vdev_state_kstats->vsk_enclosure.value.i32 = vd->vdev_enclosure;
	vdev_state_kstats->vsk_slot.value.i32 = vd->vdev_slot;

	return (0);
}

/*
 * This is a lock-less read of the pool's state (unlike using 'zpool', which
 * can potentially block for seconds).  Because it doesn't block, it can useful
 * as a pool heartbeat value.
 */
static void
vdev_state_init(vdev_t *vd)
{
	char name[KSTAT_STRLEN],
		vdev_addr[KSTAT_STRLEN];
	kstat_t *ksp = NULL;
	int value_len;

	if (vd == NULL)
		return;

	if (vd->vdev_kstat != NULL)
		return;

	if (vd->vdev_spa != NULL &&
		strcmp(spa_name(vd->vdev_spa), TRYIMPORT_NAME) == 0)
		return;

	if (vd->vdev_ops != NULL &&
	   (strcmp(vd->vdev_ops->vdev_op_type, VDEV_TYPE_ROOT) == 0 ||
	    strcmp(vd->vdev_ops->vdev_op_type, VDEV_TYPE_HOLE) == 0)) {
		return;
	}

	mutex_init(&vd->vdev_kstat_lock, NULL, MUTEX_DEFAULT, NULL);

	int n = snprintf(name, sizeof (name), "%s",  spa_name(vd->vdev_spa));
	if (n < 0) {
		zfs_dbgmsg("failed to create vdev_state kstat\n");
		return;
	}
	n = snprintf(vdev_addr, sizeof (vdev_addr), "0x%p",
		     vd);
	if (n < 0) {
		zfs_dbgmsg("failed to create vdev_addr\n");
		return;
	}

	ksp = kstat_create(name, 0, vdev_addr, "vdev",
	    KSTAT_TYPE_NAMED,
	    sizeof (vdev_state_kstat_values_t) / sizeof (kstat_named_t),
	    KSTAT_FLAG_VIRTUAL);

	vd->vdev_kstat = ksp;
	if (ksp == NULL)
		return;

	vdev_state_kstat_values_t *vdev_state_kstats =
	   kmem_alloc(sizeof (empty_vdev_state_kstats), KM_SLEEP);
	memcpy(vdev_state_kstats, &empty_vdev_state_kstats,
	    sizeof (empty_vdev_state_kstats));

	value_len = 0;
	if (vd->vdev_path != NULL) {
		value_len = strlen(vd->vdev_path) + 1;
		char *vdev_stat_path = kmem_zalloc(value_len, KM_SLEEP);
		snprintf(vdev_stat_path, value_len, "%s", vd->vdev_path);
		KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_vdev_path) = vdev_stat_path;
		KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_vdev_path) = value_len;
		ksp->ks_data_size += value_len;
	}

	value_len = 0;
	if (vd->vdev_vendor_id != NULL) {
		value_len = strlen(vd->vdev_vendor_id) + 1;
		char *vdev_vendor_id = kmem_zalloc(value_len, KM_SLEEP);
		snprintf(vdev_vendor_id, value_len, "%s", vd->vdev_vendor_id);
		KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_vid) = vdev_vendor_id;
		KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_vid) = value_len;
		ksp->ks_data_size += value_len;
	}

	value_len = 0;
	if (vd->vdev_product_id != NULL) {
		value_len = strlen(vd->vdev_product_id) + 1;
		char *vdev_product_id = kmem_zalloc(value_len, KM_SLEEP);
		snprintf(vdev_product_id, value_len, "%s", vd->vdev_product_id);
		KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_pid) = vdev_product_id;
		KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_pid) = value_len;
		ksp->ks_data_size += value_len;
	}

	value_len = 0;
	if (vd->vdev_serial_no != NULL) {
		value_len = strlen(vd->vdev_serial_no) + 1;
		char *vdev_serial_no = kmem_zalloc(value_len, KM_SLEEP);
		snprintf(vdev_serial_no, value_len, "%s", vd->vdev_serial_no);
		KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_serial) = vdev_serial_no;
		KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_serial) = value_len;
		ksp->ks_data_size += value_len;
	}

	value_len = 0;
	if (vd->vdev_devid != NULL) {
		value_len = strlen(vd->vdev_devid) + 1;
		char *vdev_stat_devid = kmem_zalloc(value_len, KM_SLEEP);
		snprintf(vdev_stat_devid, value_len, "%s", vd->vdev_devid);
		KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_devid) = vdev_stat_devid;
		KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_devid) = value_len;
		ksp->ks_data_size += value_len;
	}

	value_len = 0;
	if (vd->vdev_guid != 0UL) {
		value_len = KSTAT_STRLEN + 1;
		char *vdev_stat_guid = kmem_zalloc(value_len, KM_SLEEP);
		(void) snprintf(vdev_stat_guid, value_len, "%lu", vd->vdev_guid);
		KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_guid) = vdev_stat_guid;
		KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_guid) = value_len;
		ksp->ks_data_size += value_len;
	}

	value_len = 0;
	if (vd->vdev_hba != NULL) {
		value_len = strlen(vd->vdev_hba) + 1;
		char *vdev_stat_hba = kmem_zalloc(value_len, KM_SLEEP);
		snprintf(vdev_stat_hba, value_len, "%s", vd->vdev_hba);
		KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_hba) = vdev_stat_hba;
		KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_hba) = value_len;
		ksp->ks_data_size += value_len;
	}

	(void) snprintf(vdev_state_kstats->vsk_type.value.c,
	    sizeof (vdev_state_kstats->vsk_type.value.c), "%s",
	    vd->vdev_ops->vdev_op_type);

	vd->vdev_kstat_destroy = B_FALSE;
	ksp->ks_lock = &vd->vdev_kstat_lock;
	ksp->ks_data = vdev_state_kstats;
	ksp->ks_update = vdev_state_update;
	ksp->ks_private = vd;
	//kstat_set_raw_ops(ksp, NULL, spa_state_data, spa_state_addr);
	kstat_install(ksp);
}

static void
vdev_state_destroy(vdev_t *vd)
{
	kstat_t *ksp = vd->vdev_kstat;
	if (ksp == NULL)
		return;

	vd->vdev_kstat_destroy = B_TRUE;
	mutex_enter(&vd->vdev_kstat_lock);

	vdev_state_kstat_values_t *vdev_state_kstats = ksp->ks_data;

	if (KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_vdev_path) != NULL)
	kmem_free(KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_vdev_path),
	    KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_vdev_path));

	if (KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_vid) != NULL)
	kmem_free(KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_vid),
	    KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_vid));

	if (KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_pid) != NULL)
	kmem_free(KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_pid),
	    KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_pid));

	if (KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_serial) != NULL)
	kmem_free(KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_serial),
	    KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_serial));

	if (KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_devid) != NULL)
	kmem_free(KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_devid),
	    KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_devid));

	if (KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_guid) != NULL)
	kmem_free(KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_guid),
	    KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_guid));

	if (KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_hba) != NULL)
	kmem_free(KSTAT_NAMED_STR_PTR(&vdev_state_kstats->vsk_hba),
	    KSTAT_NAMED_STR_BUFLEN(&vdev_state_kstats->vsk_hba));

	kmem_free(vdev_state_kstats, sizeof (empty_vdev_state_kstats));

	mutex_exit(&vd->vdev_kstat_lock);
	kstat_delete(ksp);
	vd->vdev_kstat = NULL;

	mutex_destroy(&vd->vdev_kstat_lock);
}

void
vdev_stats_init(vdev_t *vd)
{
	vdev_state_init(vd);
}

void
vdev_stats_destroy(vdev_t *vd)
{
	vdev_state_destroy(vd);
}
